Skip to content

[lts_03_2025] CI: migrate to Microsoft-hosted agents (retired ACR/self-hosted pools break all PRs) - #2747

Open
Ewerton Scaboro da Silva (ewertons) wants to merge 9 commits into
lts_03_2025from
ci/lts_03_2025-hosted-agents
Open

[lts_03_2025] CI: migrate to Microsoft-hosted agents (retired ACR/self-hosted pools break all PRs)#2747
Ewerton Scaboro da Silva (ewertons) wants to merge 9 commits into
lts_03_2025from
ci/lts_03_2025-hosted-agents

Conversation

@ewertons

@ewertons Ewerton Scaboro da Silva (ewertons) commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Problem

C-canary cannot run on lts_03_2025. Every Linux job pulls containers from csdkcontainerregistry.azurecr.io via the csdk-containers endpoint and targets the retired sdk-c--* self-hosted pools. main moved off both in #2701.

Setup fails in Initialize containers:

Could not fetch access token for ACR. Please configure Managed Service Identity (MSI)
for Azure Container Registry with the appropriate permissions.

Setup gates all stages, so every PR to this branch is red before anything compiles. Not transient.

Change

Takes main's build/.vsts-ci.yml and the scripts it drives — the eight pipeline commits made on main since this branch diverged at 5a48d3fd8:

Commit PR
c59df07db Update CODEOWNERS and ubuntu-20.04 runner pool (deprecated) (#2677)
5f11fab75 Run Cleanup stage even if cancelled (#2682)
c5d43cf83 Replace pipeline with Microsoft-hosted agents (#2701)
891fa3074 Self-sufficient hosted-agent optimizations (#2718)
1317b5724 Make AZURE_LOCATION a runtime parameter (#2717)
64736930a Replace Node.js check_submodules with PowerShell (#2721)
fa6ad5d5a Disable NTLM in linux-setup-raspberry.sh (#2733)
c86b2d6a5 Make CodeQL opt-in per job (#2743)

Applied as an end state, not eight cherry-picks: the intermediate commits conflict in build_all/linux/run_tests.sh, and only the state main runs green today matters.

Plus one source fix required to build on hosted Windows agents — ca8b9bf35 (#2681), cherry-picked:

-if (reason == IOTHUB_CLIENT_CONNECTION_OK && result == IOTHUB_CLIENT_CONFIRMATION_OK)
+if (result == IOTHUB_CLIENT_CONNECTION_AUTHENTICATED && reason == IOTHUB_CLIENT_CONNECTION_OK)

result is an IOTHUB_CLIENT_CONNECTION_STATUS; IOTHUB_CLIENT_CONFIRMATION_OK belongs to a different enum. Newer MSVC rejects the mismatched comparison and this sample builds warnings-as-errors. Behaviour-preserving — both constants are 0 (verified by compiling the macro expansion).

No submodule pointer changes, no version bump.

Verification

Build 162560: all 34 jobs green, 37/37 checks. First green C-canary run on this branch.

Locally verified before pushing: enum constants both 0; fixed sample compiles clean under gcc -Wall -Wextra. MSVC and the docker cross-compiles could not be run locally and were confirmed by CI.

Note

lts_03_2025 reaches maintenance end on 2026-10-07, so this restores the gate for the remaining ~6 weeks.

#2746 cannot go green until this lands.

The C-canary pipeline on this branch cannot run at all. Every Linux job
pulls its container from csdkcontainerregistry.azurecr.io via the
csdk-containers endpoint, and runs on the sdk-c--* self-hosted pools. Both
the registry and those pools were retired; main moved off them in #2701.

On this branch the first job of the Setup stage therefore fails in
"Initialize containers" with:

  Could not fetch access token for ACR. Please configure Managed Service
  Identity (MSI) for Azure Container Registry with the appropriate
  permissions.

Setup gates everything, so all downstream stages are skipped and any PR
targeting lts_03_2025 is red before a line is compiled. Observed on
build 162517 (PR #2746), which failed 14s in.

Bring the CI configuration to parity with main by taking main's version of
the pipeline and the build/test scripts it drives. This corresponds to the
eight pipeline commits made on main since this branch diverged:

  c59df07  Update CODEOWNERS and ubuntu-20.04 runner pool (deprecated) (#2677)
  5f11fab  Run Cleanup stage even if cancelled (#2682)
  c5d43cf  Replace pipeline with Microsoft-hosted agents (#2701)
  891fa30  Self-sufficient hosted-agent optimizations (#2718)
  1317b57  Make AZURE_LOCATION a runtime parameter (#2717)
  6473693  Replace Node.js check_submodules with PowerShell (#2721)
  fa6ad5d  Disable NTLM in linux-setup-raspberry.sh (#2733)
  c86b2d6  Make CodeQL opt-in per job (#2743)

Taken as an end state rather than eight cherry-picks: the intermediate
commits conflict with each other in build_all/linux/run_tests.sh, and the
only state that matters is the one main runs green today.

Every file changed here is CI or test infrastructure. No product source,
no submodule pointers, and no version change.

Audited against this branch's tree before committing:
- all build_all/, jenkins/, testtools/ and samples/ paths the pipeline
  invokes exist here
- the cmake options it passes resolve the same way they do on main
  (enable_ipv6 is a c-utility option on both; -DpsX509IndividualEnrollments
  is a PowerShell parameter, not a cmake one)
- the YAML parses: 4 stages, 30 jobs, no remaining azurecr.io reference

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).
1 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@ewertons

Copy link
Copy Markdown
Contributor Author

Status note: build 162518 has been sitting in notStarted for ~22 minutes, queued behind roughly 15 concurrent builds across the azure-iot-sdks org (horton-*, Java, C#, Python gates). That is hosted-agent capacity, not a problem with this change.

One thing already confirmed by this run, though: the stage graph now resolves to Requirements / Setup create_azure_resources / Tests / Cleanup and the jobs are queued for real agents. On build 162517 the old YAML died 14s in at Initialize containers before any job was scheduled. So the ACR blocker is gone; what remains is whether the jobs themselves pass against this branch's older source.

Please check the completed run before merging — as noted above, I could not build or test locally, and a genuine LTS-only failure is plausible given the ~14-month source gap.

My previous commit took samples/dockerbuilds/myapp/CMakeLists.txt from main
along with the pipeline files. That file is not CI infrastructure, and main's
version links c_logging_v2:

  target_link_libraries(myapp ... aziotsharedutil c_logging_v2 parson ...)

main can do that because its root CMakeLists.txt add_subdirectory()s
deps/c-logging and installs the v2 target. This branch has no c-logging
wiring in its root CMakeLists.txt at all, so libc_logging_v2 is never built
or installed here and the cross-compile samples fail at link:

  ld: cannot find -lc_logging_v2

Observed on build 162518 in both Cross Compile (ARM) and Cross Compile
(MIPS32). Restore this branch's line, which links parson and not c_logging_v2.

Note both branches pin deps/c-logging at the same commit (504e7d47); the
difference is purely that main wires it into the build and this branch
does not.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@ewertons

Copy link
Copy Markdown
Contributor Author

First full run completed — 30 jobs green, 6 failures, both root-caused and fixed

Build 162518 got the whole matrix running for the first time on this branch. 30 jobs passed, including every Linux job, all four valgrind/helgrind suites, the E2E jobs and OSX/Xcode. The ACR blocker is gone.

Six jobs failed, from exactly two root causes. Both were my fault, not pre-existing breakage, and both are now fixed.


1. Cross Compile (ARM) and Cross Compile (MIPS32) — ld: cannot find -lc_logging_v2

My first commit took samples/dockerbuilds/myapp/CMakeLists.txt from main along with the pipeline files. That file is not CI infrastructure. main's version links c_logging_v2:

target_link_libraries(myapp ... aziotsharedutil c_logging_v2 parson ...)

main can do that because its root CMakeLists.txt does add_subdirectory(deps/c-logging) and installs the v2 target. This branch has no c-logging wiring in its root CMakeLists.txt at all, so libc_logging_v2 is never built or installed here, and the sample fails at link.

Worth noting: both branches pin deps/c-logging at the same commit (504e7d47). The difference is purely that main wires it into the build and this branch does not — so this is not a submodule problem and does not call for a submodule bump on an LTS branch.

Fixed in d25ef45f5 by restoring this branch's own line. samples/dockerbuilds/myapp/CMakeLists.txt is now byte-identical to lts_03_2025.

2. All four Windows jobs — error C2220 in iothub_client_sample_hsm.c(45,61)

Each of the four Windows jobs reported exactly 1 Error(s), all on the same line. This is a genuine pre-existing bug on this branch, exposed only because the migration moved Windows onto hosted windows-2022 images with a newer MSVC:

if (reason == IOTHUB_CLIENT_CONNECTION_OK && result == IOTHUB_CLIENT_CONFIRMATION_OK)

result is an IOTHUB_CLIENT_CONNECTION_STATUS, but IOTHUB_CLIENT_CONFIRMATION_OK is an IOTHUB_CLIENT_CONFIRMATION_RESULT — a different enum. Newer MSVC flags the mismatched-enum comparison, and this sample builds warnings-as-errors.

main already fixed exactly this in ca8b9bf35 (#2681, "caught by latest VS2022 vs17.14.2"), a one-line change. Cherry-picked here as c52197221.

Verified locally that this is behaviour-preserving. Both constants evaluate to 0, so runtime behaviour is identical and only the type error is corrected:

CONFIRMATION_OK=0 CONNECTION_AUTHENTICATED=0 equal=YES

I also compiled the fixed sample with gcc -Wall -Wextra -fsyntax-only: clean.

This is the one place where this PR now touches non-CI source. It is a single line, it is already on main, and without it no Windows job on this branch can build.


Other things I checked

  • The remaining samples/dockerbuilds/*/Dockerfile changes are toolchain modernization only (OpenSSL 1.1.1v→3.0.15, curl 7.60→8.20 with --with-openssl/--disable-ntlm, CMAKE_POLICY_VERSION_MINIMUM=3.5 for CMake 4 in newer base images). None reference c_logging_v2 or anything else main-only.
  • The Free memory is lower than 5% messages in the MIPS32 job are warnings emitted alongside the real failure, not the cause; the job died on the link error.

Still to confirm

Build 162560 is running now with both fixes. Please read that run before merging — I have verified the causes locally but cannot run MSVC or the docker cross-compiles here.

@ewertons

Copy link
Copy Markdown
Contributor Author

✅ Fully green — build 162560 succeeded

All 34 jobs passed, 37/37 GitHub checks green. Both fixes are confirmed by CI:

  • Cross Compile (ARM) and Cross Compile (MIPS32) — now pass (were ld: cannot find -lc_logging_v2).
  • Windows x86 / x64 (Debug) / x64 (Release) / Dynamic — now pass (were error C2220 in iothub_client_sample_hsm.c(45,61)).

Everything that passed in the previous run still passes, including all valgrind/helgrind suites, the E2E jobs, OSX/Xcode, mbedTLS 2.16/2.28/3.x, WolfSSL, BearSSL and RIOT/DICE. No flakes, no reruns.

This is the first green C-canary run on lts_03_2025. Ready for review.

Recommended: merge this first, then rerun CI on #2746 (which is still red only because its branch predates this pipeline).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR restores CI viability on lts_03_2025 by migrating the Azure DevOps pipeline off retired self-hosted pools/ACR images onto Microsoft-hosted agents, and updates the supporting build/test scripts (plus one sample build fix) so the hosted-agent lanes run successfully.

Changes:

  • Replace container/self-hosted pool usage in build/.vsts-ci.yml with Microsoft-hosted vmImage jobs, including new Requirements/Setup/Cleanup flow and artifact-based E2E env propagation.
  • Update Linux build/test automation for hosted agents (ccache integration, test sharding/parallelism changes, updated valgrind suppressions).
  • Refresh cross-compile/docker toolchains (OpenSSL 3.0.15, curl 8.20.0, NTLM disabled) and fix a sample enum comparison for newer MSVC.

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
build/.vsts-ci.yml Migrates CI to Microsoft-hosted agents; adds Requirements/Setup/Cleanup orchestration and per-run test config artifacts.
build_all/linux/setup.sh Adds ccache to Linux host dependencies for hosted-agent builds.
build_all/linux/build.sh Enables ccache via CMAKE_*_COMPILER_LAUNCHER when available.
build_all/linux/run_tests.sh Adds test-mode flags and tuned parallelism/sharding for hosted agents/valgrind tools.
testtools/scripts/linux-setup-raspberry.sh New hosted-agent Raspberry Pi cross-toolchain bootstrap script (toolchain + OpenSSL/curl/util-linux).
samples/dockerbuilds/RaspberryPi/Dockerfile Updates Raspberry Pi cross-build base/toolchain and bumps OpenSSL/curl, disables NTLM.
samples/dockerbuilds/ARM/Dockerfile Updates ARM cross-build OpenSSL/curl and adds CMake policy compatibility settings.
samples/dockerbuilds/MIPS32/Dockerfile Updates MIPS32 OpenSSL/curl, adds libatomic linkage notes and CMake find-package hardening.
jenkins/raspberrypi/Dockerfile Updates Raspberry toolchain libs (OpenSSL/curl) and disables NTLM.
jenkins/raspberrypi/setup_pi_for_e2e_tests.sh Updates curl install path/version and disables NTLM for device setup.
jenkins/raspberrypi/run_this_to_setup_a_pi_for_e2e_tests.sh Same curl update as above for Raspberry Pi setup helper.
jenkins/osx_gcc_openssl.sh Adds CMAKE_POLICY_VERSION_MINIMUM for newer CMake compatibility on hosted macOS.
jenkins/osx_xcode_native.sh Adds CMAKE_POLICY_VERSION_MINIMUM for newer CMake compatibility on hosted macOS.
jenkins/linux_c_option_test.sh Documents/avoids -Wmaybe-uninitialized due to GCC 12+ false positives on common patterns.
iothub_client/tests/global_valgrind_suppression.supp Adds suppressions for helgrind/drd noise on newer Ubuntu/valgrind/glibc.
provisioning_client/tests/common_prov_e2e/prov_valgrind_suppression.supp Broadens helgrind preload path matching and adds Ubuntu 24.04 suppression entries.
provisioning_client/samples/iothub_client_sample_hsm/iothub_client_sample_hsm.c Fixes enum comparison so newer MSVC compiles warnings-as-errors.
build_all/docs/Doxyfile Updates Doxygen project version number.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread build/.vsts-ci.yml Outdated
Comment thread build/.vsts-ci.yml
Comment thread testtools/scripts/linux-setup-raspberry.sh
Comment thread testtools/scripts/linux-setup-raspberry.sh Outdated
Comment thread build_all/linux/run_tests.sh
Comment thread build_all/docs/Doxyfile Outdated
Copying main's build_all/docs/Doxyfile carried its PROJECT_NUMBER of 1.15.0
onto this branch, whose iothub_client_version.h is 1.14.0. That contradicts
the stated scope of this change (no version bump) and made the generated
docs disagree with the SDK version.

The Doxyfile is not referenced by the pipeline, so it had no CI value here.
Restored to 1.14.0.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@ewertons

Copy link
Copy Markdown
Contributor Author

Doxyfile reverted; one unrelated infrastructure failure in the new run

Pushed b9a226490 reverting the Doxyfile version bump (thread above). Diff is now 17 files.

Build 162564 has one failure: Ubuntu 24.04 ARM64 (Cross-compile only), in the Install aarch64 cross toolchain and arm64 dev libs step, apt exit 100:

libssl-dev:arm64 : Depends: libssl3t64:arm64 (= 3.0.13-0ubuntu3.15) but it is not going to be installed
libcurl4t64:arm64 : Depends: libssl3t64:arm64 (>= 3.0.0) but it is not going to be installed
E: Unable to correct problems, you have held broken packages.

Not caused by this PR's latest change. The only delta between the fully green 162560 and this run is one line in build_all/docs/Doxyfile, and no job references that file:

$ git diff --stat d25ef45f5 b9a226490
 build_all/docs/Doxyfile | 2 +-

Cause. libssl3t64 is Multi-Arch: same, so libssl3t64:arm64 must be the exact same version as the amd64 copy already on the runner image. The step installs libssl-dev:arm64 unpinned from ports.ubuntu.com. When Ubuntu publishes a new libssl and the runner image has not been refreshed, the two pockets disagree and apt refuses. That is why this passed two hours earlier with identical pipeline content.

This is inherited from main and will recur there too, independent of this PR. 17 jobs have passed so far in this run and it is the only failure.

Suggest a rerun of that job. If it should be hardened instead, the durable fix is to install the arm64 libs pinned to the image's installed libssl3t64 version, which I would rather do on main for the reasons in the thread above. Happy to take either.

@ewertons

Copy link
Copy Markdown
Contributor Author

Retry result: all 34 build/test jobs green; the ARM64 failure was transient

Build 162564 after retrying the failed job:

  • Ubuntu 24.04 ARM64 (Cross-compile only)passed on attempt 2, unchanged code. Confirms the earlier libssl3t64:arm64 apt failure was transient mirror/image version skew, not a defect.
  • All other 33 jobs passed on attempt 1.

34/34 build and test jobs green.

The one remaining red is a retry artifact, not a code or test failure

destroy_azure_resource_group failed on attempt 2:

ERROR: (ResourceGroupNotFound) Resource group 'rg-e2d86b4202cb46289f23ac70b2dfe0f3' could not be found.
##[error]Script failed with exit code: 3

Attempt 1 of that job succeeded and deleted the group. The retry re-ran the whole Cleanup stage, so it tried to delete an already-deleted group. The step is:

set -e
az group delete --name $AZURE_RESOURCE_GROUP --yes --no-wait

az group delete returns 3 when the group is absent, and set -e turns that into a step failure. So Cleanup is not idempotent and any retried build on this branch will end red even when everything passed.

No resources are leaked — the opposite: the group was already removed.

This step is verbatim from main, so main has the same behaviour on any retry. Same reasoning as the other inherited items: better fixed there and allowed to flow down. The one-line fix is to tolerate an absent group, e.g. append || [ $? -eq 3 ], or precede it with an existence check.

State of this PR

Green on content, twice over:

Build Commit Result
162560 d25ef45f5 34/34 green, all first attempt
162564 b9a226490 34/34 green (ARM64 on attempt 2); only Cleanup red, from the retry

The delta between the two commits is one line in build_all/docs/Doxyfile, which no job reads.

Happy to either queue a fresh run so the current head shows a single clean green, or open the Cleanup idempotency fix against main — say which you prefer.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.

Suppressed comments (2)

Previously missed (1) — in code that hasn't changed since the last review.

build/.vsts-ci.yml:804

  • sudo apt-get install -y libwolfssl-dev || true suppresses package-install failures, which can make this job fail later in less obvious ways (or accidentally stop exercising WolfSSL if the build scripts ever add a fallback). It’s better to fail fast if WolfSSL headers/libs can’t be installed.
        set -e
        sudo apt-get update -qq
        sudo ./build_all/linux/setup.sh
        # wolfSSL is not in Ubuntu main; install dev headers from universe.
        sudo apt-get install -y libwolfssl-dev || true

build_all/linux/run_tests.sh:60

  • When running plain tests with --e2e, the --e2e-only flag is currently ignored (the script still runs unit tests + E2E), and --e2e --ut-only results in running no tests. This can lead to incorrect or false-green executions if someone invokes the script with these flag combinations.
if $run_plain; then
    if $run_e2e; then
        if ! $ut_only; then
            # Unit tests + E2E, no valgrind/helgrind/drd
            # iothubclient_mqtt_dt_e2e is quarantined: see GitHub issue (twin PATCH never

@ewertons

Copy link
Copy Markdown
Contributor Author

Addressing the 2 suppressed comments from the latest review

The re-review of b9a226490 generated no new inline comments, but flagged two suppressed ones. Both investigated.

1. build_all/linux/run_tests.sh — flag combinations

Confirmed, and broader than the earlier thread. Simulating the flag logic:

Invocation Runs
--e2e --e2e-only UT + E2E — --e2e-only silently ignored
--e2e --ut-only nothing
--e2e-only nothing

So there are three bad combinations, not one. In the plain branch --e2e-only is never consulted when --e2e is set, and --ut-only suppresses the only ctest call.

Not reachable from this pipeline. All six invocations resolve correctly:

RUN_TESTS_ARGS Runs
--e2e plain UT + E2E
--valgrind --ut-only valgrind UT
--valgrind --e2e --e2e-only valgrind E2E
--helgrind --ut-only helgrind UT
--helgrind --e2e --e2e-only helgrind E2E
--drd --e2e drd

Verbatim from main, so same reasoning as the other inherited items — worth a proper argument-validation fix on main covering all three combinations.

2. build/.vsts-ci.yml:804libwolfssl-dev || true

Disagree that this can silently stop exercising wolfSSL. The build cannot succeed without it:

  • jenkins/linux_wolfssl.sh configures with -Duse_wolfssl=ON -Duse_openssl=OFF.
  • With use_wolfssl set, c-utility compiles adapters/tlsio_wolfssl.c unconditionally, which includes wolfssl/options.h, wolfssl/ssl.h, wolfssl/error-ssl.h and wolfssl/wolfcrypt/types.h.
  • That script runs under set -o errexit.

If the package were missing the compile would fail on the missing headers and the Build step would fail. The || true only tolerates a failure in the apt step; the real check is at compile time, and the WolfSSL job passed.

The valid part of the point is that failing at apt time gives a clearer error than failing later at a missing include. That is a readability improvement, not a correctness gap, and the line is verbatim from main.

No change made for either. Both are covered by the follow-up I offered against main.

Fetch the E2E helper module from master
  Both call sites pulled Azure.Iot.Sdk.Test.psm1 from a personal feature
  branch on iot-sdks-e2e-fx. That branch never had a pull request, is 43
  commits behind master and 3 ahead, so CI depended on a stale dead end that
  could vanish at any time. Point both at master.

  All three commits unique to that branch are superseded on master:
    - "install stable azure-iot CLI extension by default" -> master installs a
      pinned extension version (0.30.0b2) via Install-AzureIotCliExtension,
      which is strictly more reproducible than selecting whatever --allow-preview
      resolves to.
    - "use .pem for temp cert files" -> present on master.
    - "--login on enrollment commands" -> that workaround existed because
      --allow-preview drifted onto an untested extension build; pinning the
      version removes the cause.

  Verified master still satisfies this pipeline: New-AzureResourceGroupName,
  New-AzIotTestEnvironment, New-AzIotCSDKE2ETestConfig and
  Test-SubmoduleConsistency all exist, are exported, and accept every
  parameter used here.

echo the home directory instead of executing it
  A bare $HOME ran the home directory as a command, logging
  "Is a directory" and returning 126. Harmless only because the step has no
  set -e and $HOME was not the last command.

Make the raspberry toolchain setup idempotent
  mkdir $WORK_ROOT failed on any re-run; mkdir -p and quoting fix that.
  pushd /${WORK_ROOT}/... had a stray leading slash that produced a
  double-slash path and would break outright if WORK_ROOT were relative.

Reject and correct the broken run_tests.sh flag combinations
  Three invocations misbehaved silently:
    --e2e-only            ran nothing and exited 0 (false green)
    --e2e --ut-only       ran nothing and exited 0 (false green)
    --e2e --e2e-only      ignored --e2e-only and ran unit tests too
  --e2e-only now requires --e2e, and the plain branch honours both flags.
  Verified the six invocations this pipeline actually uses produce byte
  identical ctest commands before and after.

Make resource group cleanup idempotent
  az group delete exits 3 when the group is absent, and the step runs under
  set -e. Retrying any job re-runs the Cleanup stage, so a build whose first
  cleanup succeeded went red on retry with ResourceGroupNotFound. This was
  observed on the previous run of this branch. Check for the group first.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@ewertons

Copy link
Copy Markdown
Contributor Author

All 5 review comments fixed in fd35ca602

  1. psm1 source — both call sites now fetch from master. That branch never had a PR and was 43 commits behind master; all 3 of its unique commits are superseded there (details in the thread).
  2. $HOME — now echo "$HOME".
  3. mkdir — now mkdir -p "$WORK_ROOT", idempotent.
  4. pushd — stray leading slash removed, quoted.
  5. run_tests.sh — three broken flag combinations fixed; the 6 invocations this pipeline uses emit byte-identical ctest commands, verified locally.

One addition beyond the review comments

Made Destroy Azure Resource Group idempotent. az group delete exits 3 when the group is absent and the step runs under set -e, so retrying any job re-ran the Cleanup stage and turned a build red after its first cleanup had already succeeded. That is exactly what happened on the previous run, and it would block a green result whenever a job needs a retry. Now checks for the group first — verified both paths exit 0.

Local verification

  • run_tests.sh and linux-setup-raspberry.sh pass bash -n; flag matrix exercised with a ctest stub.
  • mkdir idempotency and the cleanup no-op path tested directly.
  • YAML parses: 4 stages, 30 jobs, 0 azurecr.io references, 0 references to the old branch.

The MSVC, docker cross-compile and Azure provisioning paths cannot be exercised outside CI, so the pipeline is the check for those.

The Ubuntu 24.04 ARM64 job intermittently failed in
"Install aarch64 cross toolchain and arm64 dev libs" with apt exit 100:

  libssl-dev:arm64 : Depends: libssl3t64:arm64 (= 3.0.13-0ubuntu3.12)
                     but it is not going to be installed
  E: Unable to correct problems, you have held broken packages.

libssl3t64, libcurl4t64 and zlib1g are Multi-Arch: same, so the arm64 copy
must be the exact version of the amd64 copy already on the runner image.
ports.ubuntu.com and archive.ubuntu.com are not always in sync; when they
drift apt rejects the whole transaction.

That the drift, not the tree, is the trigger is visible in the versions:
one failure demanded 3.0.13-0ubuntu3.15 and a later one 3.0.13-0ubuntu3.12,
with no change in between, and a plain retry of the first one passed.

Name both arches for the three Multi-Arch: same runtime libraries so apt can
settle on a version present in both archives, and allow downgrades so it can
move the amd64 copy back when ports is the side that lags.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@ewertons

Copy link
Copy Markdown
Contributor Author

ARM64 job hardened in 4a07ac04e — it was not a one-off flake

The ARM64 job failed again on the previous run, so my earlier "transient" call was wrong on the recurrence, though right on the cause. Evidence it is environmental rather than tree-related:

Run ARM64 result Version apt demanded
162564 attempt 1 fail libssl3t64:arm64 (= 3.0.13-0ubuntu3.15)
162564 attempt 2 pass — (identical code)
162575 fail libssl3t64:arm64 (= 3.0.13-0ubuntu3.12)

Two different demanded versions with no change in between, and a plain retry passing, rules out this branch's contents. main passes the same job, which is why it was not caught earlier.

Cause. libssl3t64, libcurl4t64 and zlib1g are Multi-Arch: same, so the :arm64 copy must be the exact version of the :amd64 copy already on the runner image. ports.ubuntu.com and archive.ubuntu.com are not always in sync; when they drift, apt rejects the whole transaction with held broken packages. Whether a given run passes depends on which mirror snapshot the agent happens to hit.

Fix. Name both arches for those three runtime libraries so apt can settle on a version present in both archives, and pass --allow-downgrades so it can move the amd64 copy back when ports is the lagging side.

This is inherited from main and will keep biting there intermittently; same fix applies.

@ewertons

Copy link
Copy Markdown
Contributor Author

✅ Fully green — build 162584 succeeded

37/37 checks, all 35 jobs succeeded on first attempt, no retries. Head 4a07ac04e.

Every previously failing job is now green, including the two that were failing for real reasons:

Job Was Now
Ubuntu 24.04 ARM64 apt held broken packages (multiarch drift) pass
Cross Compile ARM / MIPS32 ld: cannot find -lc_logging_v2 pass
Windows x86 / x64 Debug / x64 Release / Dynamic error C2220 (mismatched enum) pass
create_azure_resources, Check Submodules untested against master psm1 pass
destroy_azure_resource_group red on any retry pass

Notably create_azure_resources and all E2E jobs passed while fetching the helper module from master, which confirms the branch pin was not load-bearing.

All 5 review comments are addressed, plus the two robustness fixes needed to make the result reproducible rather than luck-of-the-agent. Ready to merge.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.

Suppressed comments (2)

Previously missed (2) — in code that hasn't changed since the last review.

build/.vsts-ci.yml:813

  • The Host setup step installs libwolfssl-dev with || true, which can mask install failures and cause later CMake/configure failures that are harder to diagnose. Since the wolfSSL build relies on system headers (jenkins/linux_wolfssl.sh doesn’t build wolfSSL itself), fail fast by removing || true (or explicitly check availability and error out).
        set -e
        sudo apt-get update -qq
        sudo ./build_all/linux/setup.sh
        # wolfSSL is not in Ubuntu main; install dev headers from universe.
        sudo apt-get install -y libwolfssl-dev || true

build/.vsts-ci.yml:109

  • There’s a stray + at the end of this comment line (first job+). While harmless, it looks like an accidental artifact and reduces readability.
      # stage rerun, or an agent-loss auto-retry). Publish only on the first job+

@ewertons

Copy link
Copy Markdown
Contributor Author

Latest review: no new inline comments; 2 suppressed items

Copilot re-reviewed 4a07ac04e — 17/17 files, no new comments. Both suppressed items are pre-existing lines, no change made:

1. libwolfssl-dev ... || true (line 813) — repeat of an item I answered earlier. The premise that it can silently skip wolfSSL is wrong: jenkins/linux_wolfssl.sh configures with -Duse_wolfssl=ON, which makes c-utility compile adapters/tlsio_wolfssl.c, which includes wolfssl/ssl.h; the script runs under set -o errexit. A missing package fails the build at compile time. Valid only as a fail-faster/clearer-error preference, and the line is verbatim from main.

2. "Stray +" (line 109) — not stray. The comment wraps mid-phrase: "Publish only on the first job+ / stage attempt", i.e. job+stage attempt, which is exactly what the condition checks:

condition: and(always(), eq(variables['System.JobAttempt'], '1'), eq(variables['System.StageAttempt'], '1'))

Both JobAttempt and StageAttempt must be 1. Removing the + would make the sentence say something the code does not do.

build/.horton-e2e.yml on this branch predates two changes that have since
happened outside it, and cannot run as written:

1. It names the service connection 'GitHub OAuth - az-iot-builder-01'.
   That endpoint no longer exists in the azure-iot-sdks project; the GitHub
   connection is now 'azure-iot-sdk-python-github'. Resource resolution fails
   before any job starts.

2. It declares `jobs:` with jobs-gate-c.yaml directly, with no setup stage.
   That template is consumed from iot-sdks-e2e-fx@refs/heads/master, which is
   a moving ref, and it now begins with steps-load-test-config.yaml. That step
   does `download: current / artifact: test_config`, published by
   steps-create-azure-resources.yaml. With no setup stage nothing publishes
   that artifact, so every job fails on the download.

Take main's version, which adds the setup and cleanup stages around the gate
and uses the endpoint that exists. The file references only e2e-fx templates
and no paths inside this repo, so it is branch agnostic; the SDK itself is
cloned by the gate from Horton.Repo at Horton.Commit.

Note this file alone does not make the gate run for this branch: the
horton-c-gate pipeline's pull request trigger is filtered to +main, so PRs
targeting lts_03_2025 are reported as skipped. That filter has to be widened
separately for the gate to execute here.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@ewertons

Copy link
Copy Markdown
Contributor Author

Why horton-c-gate shows as skipped

It is filtered out by pipeline configuration, not by anything in this PR. The horton-c-gate pipeline's pull request trigger is:

"branchFilters": ["+main"],
"triggerType": "pullRequest"

Only PRs whose target branch is main match. This PR targets lts_03_2025, so Azure Pipelines reports the check as skipped (GitHub conclusion NEUTRAL). Its last 12 runs are all either refs/heads/main or PRs #2744/#2748, both of which target main. The build-completion trigger is filtered the same way (+public-preview, +main), so it does not chain from C-canary here either.

Enabling the filter alone would not have worked

build/.horton-e2e.yml on this branch is stale in two independently fatal ways:

  1. It names the service connection 'GitHub OAuth - az-iot-builder-01'. That endpoint no longer exists in the project — the GitHub connection is now azure-iot-sdk-python-github. Resource resolution fails before any job starts.
  2. It declares jobs: with jobs-gate-c.yaml directly and no setup stage. That template is consumed from iot-sdks-e2e-fx@refs/heads/master — a moving ref — and now starts with steps-load-test-config.yaml, which does download: current / artifact: test_config, published by steps-create-azure-resources.yaml. With no setup stage that artifact is never published, so every job fails on the download.

Same root cause as the rest of this PR: this branch's CI config drifted while the shared templates it consumes moved on.

Fixed here

affd1c5d3 takes main's build/.horton-e2e.yml, which wraps the gate in setup / build_and_test / cleanup and uses the endpoint that exists. The file references only e2e-fx templates and no paths inside this repo, so it is branch-agnostic — the SDK is cloned by the gate from Horton.Repo at Horton.Commit. Verified it parses and that main's version passes on PRs #2744 and #2748.

Still needed

Widening the pipeline's PR trigger to ["+main", "+lts_03_2025"] is a pipeline setting, not a repo change. I have requested that edit; it needs an approval I do not hold. Until it is applied the gate will keep reporting skipped here, now for configuration reasons only rather than because the file is broken.

Worth deciding separately whether the build-completion trigger should also include lts_03_2025 so the gate chains after C-canary on that branch post-merge.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated no new comments.

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

build/.vsts-ci.yml:69

  • Setup is configured with dependsOn: [], so create_azure_resources can run (and provision Azure resources) even if the Requirements/checksubmodule stage fails. This defeats the submodule-gating goal and can waste limited provisioning quota on known-bad runs.
- stage: Setup
  dependsOn: [] # Run in parallel with Requirements to save ~3-5 min

@ewertons

Copy link
Copy Markdown
Contributor Author

Build 162609 green — 37/37, no retries

Head affd1c5d3 (horton yaml port). All 35 jobs succeeded first attempt.

Latest review: 18/18 files, no new inline comments; 1 suppressed item

Setup has dependsOn: [] (line 69) — the claim that this "defeats the submodule-gating goal" is not correct. The stage graph is:

Stage dependsOn condition
Requirements default
Setup [] default
Tests [Requirements, Setup] default
Cleanup [Setup, Tests] always()

Tests depends on Requirements with the default succeeded() condition, so a submodule mismatch still skips every test job. The gate holds.

The accurate part is the quota point: on a failing-submodule run the resource group is provisioned and then torn down without being used. Cleanup is always(), so nothing leaks. That is the documented tradeoff in the inline comment — running Setup in parallel saves 3-5 minutes on every run, against provisioning briefly on the rare run that fails submodule validation.

Verbatim from main; no change made.

@ewertons

Copy link
Copy Markdown
Contributor Author

Trigger request widened to +lts_*

Yes, wildcards work in classic branch filters — and this repo already relies on it. C-canary (definition 86, same repo) uses +lts_*, which is exactly why C-canary runs on this PR while horton-c-gate skips:

Pipeline PR trigger branch filters
C-canary +uhttp_upload, +public-preview-pnp, +public-preview, **+lts_\***, +main
horton-c-gate +main

Five integrate-into-repo-* pipelines use +master,+lts_* too, so the pattern is well established here.

Requested edit is now ["+main", "+lts_*"] instead of pinning one branch. Matches all 10 lts_ branches (lts_01_2021lts_08_2025-era, including lts_03_2025), so future LTS branches are covered without another pipeline edit. PR triggers match on the target branch, so topic branches like ci/lts_03_2025-hosted-agents are unaffected.

Two notes:

  • This also gates PRs to LTS branches that are past end of support. Harmless — they rarely see PRs — but it is broader than strictly needed.
  • The build-completion trigger is still ["+public-preview", "+main"], so the gate will not chain after C-canary on LTS branches post-merge. I left it alone; adding +lts_* there is a one-line change if you want that too, but it starts running live E2E on every LTS merge.

Still needs the pipeline-setting approval; the repo-side fix (affd1c5d3) is already in.

@ewertons

Copy link
Copy Markdown
Contributor Author

Trigger change not applied — horton-c-gate stays skipped

The pipeline-settings edit to widen horton-c-gate's PR trigger to ["+main", "+lts_*"] was not approved, so definition 204 is unchanged (PR trigger still ["+main"]).

Consequence: horton-c-gate will keep reporting skipped on this PR and any PR targeting an lts_* branch. That is a pipeline configuration decision, not a repo problem, and nothing in this PR can change it.

The repo-side half is still worth having and stays in: affd1c5d3 replaces this branch's stale build/.horton-e2e.yml, which named a service connection that no longer exists ('GitHub OAuth - az-iot-builder-01') and declared jobs: with no setup stage, so it could not have run even with the trigger enabled. If the gate is ever enabled for LTS branches, the file is now correct.

C-canary is unaffected: 37/37 green, all 35 jobs first attempt.

The horton-c-gate pull request trigger was widened to +main,+lts_* so the
gate applies to LTS branches. Branch filters are evaluated when a pull
request event fires, so the already-reported skip on this PR does not
re-evaluate on its own. Empty commit to raise a new event.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@ewertons

Copy link
Copy Markdown
Contributor Author

horton-c-gate now runs here — and found a real gate bug, fixed in Azure/iot-sdks-e2e-fx#445

Trigger is applied (definition 204 rev 26, PR filters ["+main", "+lts_*"]). Branch filters are evaluated when a PR event fires, so the already-reported skip did not re-evaluate on its own; empty commit b3d640f33 raised a new event and the gate ran for the first time on an LTS branch.

The ported .horton-e2e.yml works. setup / create_azure_resources succeeded, which confirms both problems it fixed — the missing service connection and the absent test_config artifact. cleanup / destroy_azure_resource_group also passed.

build_and_test / build_linux_amd64 failed, in the gate's own docker build:

/wrapper/build/azure_macro_utils/macro_utils.h:1:10: fatal error:
macro_utils/macro_utils.h: No such file or directory

Cause is in the gate, not this branch. The image is built in two phases: prebuild.sh builds the SDK's default branch to warm the image, then rebuild.sh checks out the commit under test and rebuilds. rebuild.sh clears CMakeCache.txt but not generated headers.

main pins macro-utils-c at 045636ac, which ships inc/macro_utils/, so the SDK generates a forwarder at /azure_macro_utils/macro_utils.h#include "macro_utils/macro_utils.h". CMAKE_BINARY_DIR is /wrapper/build because the wrapper does add_subdirectory(${C_SDK_ROOT}).

lts_03_2025 pins 5926caf4, which ships inc/azure_macro_utils/ and has no macro_utils/ directory. The phase-1 forwarder survives, sits on the include path ahead of ${C_SDK_ROOT}/deps/azure-macro-utils-c/inc, shadows the real header, and its inner include resolves to nothing.

The existing symlink guard in rebuild.sh covers only the opposite direction and correctly does nothing here.

Fix: Azure/iot-sdks-e2e-fx#445 removes the stale forwarding headers before configuring. Safe both ways — an SDK that needs them regenerates them during the following cmake. Verified by reproducing the shadowing with both layouts at the real include order: stale forwarder reproduces the error exactly, removing it builds clean, default-branch layout unaffected.

This is a pre-existing gate limitation that only surfaced because the gate had never run against an LTS branch before. Nothing to change in this PR.

Sequencing: the gate consumes e2e-fx templates from refs/heads/master, so once #445 merges, a rerun here picks it up automatically. Until then horton-c-gate will fail on this PR for that reason alone. C-canary is unaffected and healthy.

@ewertons

Copy link
Copy Markdown
Contributor Author

C-canary 162617: 34/35 green; one flake in iothubclient_uploadtoblob_e2e_helgrind

Run Tests (--helgrind --e2e --e2e-only) exited 8 with 17 of 18 tests passing:

94% tests passed, 1 tests failed out of 18
	183 - iothubclient_uploadtoblob_e2e_helgrind (Failed)

Not introduced by this PR. build_all/linux/run_tests.sh on this branch is byte-identical to main in the helgrind section, including the exclusion list, which quarantines only iothubclient_mqtt_dt_e2e.

Intermittent, not a regression. The same job passed on the four previous builds of this branch, all first attempt:

Build helgrind_e2e
162560 pass
162564 pass
162584 pass
162609 pass
162617 fail

That test is known to be timing-sensitive under helgrind. It was quarantined in #2701 with the note that helgrind instrumentation makes IoTHubClient_Destroy() exceed the test's 30s deadline on the 4-vCPU hosted agents, then deliberately un-quarantined in #2718. So main carries the same exposure.

I have requested a retry of the failed job. If it should be made deterministic instead, the options are re-adding it to the helgrind exclusion list or raising that test's deadline — both decisions belong on main, since main un-quarantined it on purpose. Happy to open that PR.

Retrying only the failed helgrind E2E job could not succeed: the Cleanup
stage runs on build completion with always(), so it had already deleted the
resource group before the retry started. A full run re-executes Setup and
provisions resources first.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@ewertons

Copy link
Copy Markdown
Contributor Author

The retry could not have passed — it ran after resources were deleted

Retrying only the failed job was the wrong remedy, and the 17/18 result is an artifact of that, not new signal.

05:37:02-05:37:10  Cleanup attempt 1 destroyed the resource group
05:49:37           helgrind E2E retry started (12 min later)
06:28:19           retry finished: 17 of 18 failed

The Cleanup stage runs with condition: always(), so when the build first completed as failed it deleted the resource group. The retry then ran every E2E test against Azure resources that no longer existed.

Confirming detail: the only test that passed on the retry was iothub_invalidcert_e2e_helgrind — the one test that does not need a live hub.

So the two attempts say different things:

Attempt Result Meaning
1 1/18 failed (iothubclient_uploadtoblob_e2e_helgrind) real signal
2 17/18 failed resources deleted; not usable signal

Structural, inherited from main: "Rerun failed jobs" can never work for E2E in this pipeline, because Cleanup has already torn down the environment. The correct remedy is a full run, which re-executes Setup. Pushed an empty commit to do that.

Worth fixing properly on main — e.g. skipping the teardown when a rerun is possible, or making E2E jobs re-provision. I have not attempted that redesign here; happy to if you want it.

On the underlying flake

iothubclient_uploadtoblob_e2e_helgrind has now failed once in five runs of this branch. It is a known-fragile test: #2701 quarantined it under helgrind with the note that instrumentation makes IoTHubClient_Destroy() exceed the test's 30s deadline on 4-vCPU hosted agents, and #2718 removed that quarantine. main carries the same exposure today.

If it recurs on this run I will re-quarantine it under helgrind here, using #2701's own justification. Holding off for now rather than reducing coverage preemptively on a single data point.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants